package com.unicloud.medical.minio;

import com.unicloud.medical.oss.common.AbstractOSSService;
import com.unicloud.medical.oss.common.constant.CommonConstant;
import com.unicloud.medical.oss.common.constant.OSSTypeConstant;
import com.unicloud.medical.oss.common.enums.ErrorEnum;
import com.unicloud.medical.oss.common.model.BucketInfo;
import com.unicloud.medical.oss.common.model.FileMetaData;
import com.unicloud.medical.oss.common.model.OSSServerProperties;
import com.unicloud.medical.oss.common.model.ObjectInfo;
import com.unisound.medical.common.exception.BusinessException;
import com.unisound.medical.common.util.AssertUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Setter
@NoArgsConstructor
public class MinioService extends AbstractOSSService {
    private String name;
    private OSSServerProperties minioProperties;
    private MinioClient minioClient;

    @Override
    public String type() {
        return OSSTypeConstant.MINIO;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String endpoint() {
        return minioProperties.getEndpoint();
    }

    @Override
    public String proxyEndpoint() {
        return minioProperties.getProxyEndpoint();
    }

    @Override
    public Boolean bucketExist(String bucketName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            log.error("bucketExist error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 创建桶
     * @param bucketName
     */
    @Override
    public Boolean createBucket(String bucketName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        try {
            Boolean bucketExists = bucketExist(bucketName);
            if (bucketExists) return true;

            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            return true;
        } catch (Exception e) {
            log.error("createBucket error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 设置桶策略
     * @param bucketName
     * @param policyText
     */
    @Override
    public Boolean setBucketPolicy(String bucketName, String policyText) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(policyText), ErrorEnum.POLICY_TEXT_INVALID);
        try {
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyText).build());
        } catch (Exception e) {
            log.error("setBucketPolicy error. cause={}", e.getMessage());
        }
        return true;
    }

    /**
     * 删除存储桶
     */
    @Override
    public Boolean removeBucket(String bucketName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            return true;
        } catch (Exception e) {
            log.error("removeBucket error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 获取全部桶
     */
    @Override
    public List<BucketInfo> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            if (CollectionUtils.isEmpty(buckets)) return null;

            return buckets.stream()
                    .map(bucket -> new BucketInfo(bucket.name(), bucket.creationDate().toString()))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("getAllBuckets error. cause={}", e.getMessage());
            return null;
        }
    }

    /**
     * 查看桶内所有的文件对象
     */
    @Override
    public List<ObjectInfo> listAllObjects(String bucketName) {
        return listObjects(bucketName, null);
    }

    private List<ObjectInfo> buildObjectInfos(Iterable<Result<Item>> results) {
        if (Objects.isNull(results)) return null;

        List<ObjectInfo> objectInfos = new ArrayList<>();
        for (Result<Item> result : results) {
            try {
                Item item = result.get();
                if (Objects.nonNull(item)) {
                    objectInfos.add(new ObjectInfo(item.objectName(), item.size()));
                }
            } catch (Exception e) {
                log.error("buildObjectInfos error. cause={}", e.getMessage());
            }
        }
        return objectInfos;
    }

    /**
     * 查看桶内指定文件夹的文件对象
     */
    @Override
    public List<ObjectInfo> listObjects(String bucketName, String folder) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        ListObjectsArgs.Builder builder = ListObjectsArgs.builder().bucket(bucketName);
        if (StringUtils.isNotEmpty(folder)) {
            builder.prefix(folder).recursive(true);
        }
        Iterable<Result<Item>> results = minioClient.listObjects(builder.build());
        return buildObjectInfos(results);
    }

    /**
     * 文件上传
     *
     * @param file 文件
     * @return String
     */
    @Override
    public String uploadObject(String bucketName, String objectName, FileMetaData file) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(Objects.nonNull(file) && Objects.nonNull(file.getData()) && StringUtils.isNotEmpty(file.getContentType()) && file.getSize() > 0, ErrorEnum.FILE_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.OBJECT_NAME_INVALID);
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .contentType(file.getContentType())
                    .stream(new ByteArrayInputStream(file.getData()), file.getSize(), -1)
                    .build();

            // 文件名称相同会覆盖
            minioClient.putObject(objectArgs);
            return accessUrl(bucketName, objectName);
        } catch (Exception e) {
            log.error("uploadObject error. cause={}", e.getMessage());
            return null;
        }
    }

    public Boolean uploadLocalObject(String bucketName, String objectName, String fileName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.OBJECT_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(fileName), ErrorEnum.FILE_NAME_INVALID);
        try {
            minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());
            return true;
        } catch (Exception e) {
            log.error("uploadLocalObject error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 根据文件名称获取文件
     * @param bucketName
     * @param objectName
     */
    @Override
    public GetObjectResponse getObject(String bucketName, String objectName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.OBJECT_NAME_INVALID);
        try {
            return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            log.error("getObject error. cause={}", e.getMessage());
            return null;
        }
    }

    /**
     * 删除
     *
     * @param objectName
     */
    @Override
    public Boolean removeObject(String bucketName, String objectName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.OBJECT_NAME_INVALID);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        } catch (Exception e) {
            log.error("removeObject error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 批量删除
     *
     * @param objectNames
     */
    @Override
    public Boolean removeObjects(String bucketName, List<String> objectNames) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(CollectionUtils.isNotEmpty(objectNames), ErrorEnum.OBJECT_NAMES_INVALID);
        try {
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(objectNames.stream()
                            .map(objectName -> new DeleteObject(objectName))
                            .collect(Collectors.toList()))
                    .build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                log.error("batchRemove error. objectName={} message={}", error.objectName(), error.message());
            }
            return true;
        } catch (Exception e) {
            log.error("batchRemoves error. cause={}", e.getMessage());
            return false;
        }
    }

    /**
     * 文件迁移
     * @param bucketName
     * @param sourceObjectName
     * @param targetObjectName
     */
    @Override
    public String copyObject(String bucketName, String sourceObjectName, String targetObjectName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(sourceObjectName), ErrorEnum.SOURCE_OBJECT_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(targetObjectName), ErrorEnum.TARGET_OBJECT_NAME_INVALID);
        try {
            minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(sourceObjectName).build()).bucket(bucketName).object(targetObjectName).build());
            return accessUrl(bucketName, targetObjectName);
        } catch (Exception e) {
            log.error("copyObject error. cause={}", e.getMessage());
            throw new BusinessException(ErrorEnum.COPY_OBJECT_FAIL);
        }
    }

    @Cacheable(CommonConstant.OSS_CACHE_NAME)
    @Override
    public String signedUrl(String bucketName, String objectName) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(bucketName), ErrorEnum.BUCKET_NAME_INVALID);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(objectName), ErrorEnum.OBJECT_NAME_INVALID);

        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs
                    .builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.HOURS)
                            .method(Method.GET)
                    .build());
        } catch (Exception e) {
            log.error("signedUrl error. cause={}", e.getMessage());
            throw new BusinessException(ErrorEnum.SIGNED_URL_FAIL);
        }
    }
}

